/*
 * Copyright (c) 2013 Qualcomm Atheros, Inc.
 *
 * See file CREDITS for list of people who contributed to this
 * project.
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License as
 * published by the Free Software Foundation; either version 2 of
 * the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston,
 * MA 02111-1307 USA
 */

#include <common.h>
#include <command.h>
#include <asm/mipsregs.h>
#include <asm/addrspace.h>
#include <config.h>
#include <version.h>
#include <pci.h>
#include <atheros.h>

/*
** PCI controller "hose" value
*/

static struct pci_controller hose;

static int ath_local_read_config(int where, int size, uint32_t *value);
static int ath_local_write_config(int where, int size, uint32_t value);

static int
ath_local_read_config(int where, int size, uint32_t *value)
{
	*value = ath_reg_rd(ATH_PCI_CRP + where);
	return 0;
}

static int
ath_local_write_config(int where, int size, uint32_t value)
{
	ath_reg_wr((ATH_PCI_CRP + where),value);
	return 0;
}

static int
ath_pci_read_config(struct pci_controller *hose,
			pci_dev_t dev, int where, uint32_t *value)
{
	*value = ath_reg_rd(ATH_PCI_DEV_CFGBASE + where);
	return 0;
}

static int
ath_pci_write_config(struct pci_controller *hose,
			pci_dev_t dev, int where, uint32_t value)
{
	ath_reg_wr((ATH_PCI_DEV_CFGBASE + where), value);
	return 0;
}

#ifdef PCIE2_APP_ADDRESS
static int
ath_local_read_config_rc2(int where, int size, uint32_t *value)
{
	*value = ath_reg_rd(0x18250000 + where);
	return 0;
}

static int
ath_local_write_config_rc2(int where, int size, uint32_t value)
{
	ath_reg_wr((0x18250000 + where),value);
	return 0;
}

static int
ath_pci_read_config_rc2(struct pci_controller *hose,
			pci_dev_t dev, int where, uint32_t *value)
{
	*value = ath_reg_rd(0xb6000000 + where);
	return 0;
}

static int
ath_pci_write_config_rc2(struct pci_controller *hose,
			pci_dev_t dev, int where, uint32_t value)
{
	ath_reg_wr((0xb6000000 + where), value);
	return 0;
}
#endif

/*
** We will use the ART configuration information stored in flash to initialize
** these devices as required.
*/

#ifdef CONFIG_PCI_PLATDEV_INIT_NOOP
void plat_dev_init(void){}
#else
void plat_dev_init(void)
{
	u32	val;
	u32	addr;
	u32	BaseAddr = 0x10000000;
	u32	CalAddr = WLANCAL;
	volatile u16 *calData;

	/*
	 * Copy the device ID from Flash to device config space.
	 */

	calData = (u16 *)CalAddr;

#ifndef CONFIG_PCI_CONFIG_DATA_IN_OTP
	if (calData[0] != 0xa55a && calData[0] != 0x5aa5)
	{
#ifndef COMPRESSED_UBOOT
		prmsg("BOARD IS NOT CALIBRATED!!!\n");
#endif
		return;
	}
#else
	return;
#endif
	/*
	** Need to setup the PCI device to access the internal registers
	*/
	if ((is_ar7241() || is_ar7242()))
		ath_pci_write_config(&hose, NULL, 0x10, 0x1000ffff);
	else
		ath_pci_write_config(&hose, NULL, 0x10, 0xffff);

	ath_pci_write_config(&hose, NULL, 0x04, 0x6);

#ifdef PCIE2_APP_ADDRESS
	ath_pci_write_config_rc2(&hose, NULL, 0x10, 0xffff);

	ath_pci_write_config_rc2(&hose, NULL, 0x04, 0x6);
#endif

	/*
	** Set pointer to first reg address
	*/

	calData += ATH_ART_PCICFG_OFFSET;

	while(*calData != 0xffff)
	{
		u16 cd;

		cd = *calData++;
		addr = BaseAddr + cd;
		val = *calData++;
		val |= (*calData++) << 16;

		ath_reg_wr_nf(addr,val);
		udelay(100);
	}

	return;
}
#endif

/******************************************************************************/
/*!
** \brief pci host initialization
**
** Sets up the PCI controller on the host. For AR7240 this may not be necessary,
** but this function is required for board support.
**
** We want a 1:1 mapping between PCI and DDR for inbound and outbound.
** The PCI<---AHB decoding works as follows:
**
** 8 registers in the DDR unit provide software configurable 32 bit offsets
** for each of the eight 16MB PCI windows in the 128MB. The offsets will be
** added to any address in the 16MB segment before being sent to the PCI unit.
**
** Essentially for any AHB address generated by the CPU,
** 1. the MSB four bits are stripped off, [31:28],
** 2. Bit 27 is used to decide between the lower 128Mb (PCI) or the rest of
**    the AHB space
** 3. Bits 26:24 are used to access one of the 8 window registers and are
**    masked off.
** 4. If it is a PCI address, then the WINDOW offset in the WINDOW register
**    corresponding to the next 3 bits (bit 26:24) is ADDED to the address,
**    to generate the address to PCI unit.
**
**     eg. CPU address = 0x100000ff
**         window 0 offset = 0x10000000
**         This points to lowermost 16MB window in PCI space.
**         So the resulting address would be 0x000000ff+0x10000000
**         = 0x100000ff
**
**         eg2. CPU address = 0x120000ff
**         WINDOW 2 offset = 0x12000000
**         resulting address would be 0x000000ff+0x12000000
**                         = 0x120000ff
**
** There is no translation for inbound access (PCI device as a master)
**
**  \return N/A
*/

#ifdef  COMPRESSED_UBOOT
#	define PCI_INIT_RET_TYPE	int
#	define PCI_INIT_RETURN		return 0
#else
#	define PCI_INIT_RET_TYPE	void
#	define PCI_INIT_RETURN		return
#endif

PCI_INIT_RET_TYPE
pci_init_board (void)
{
#ifdef CONFIG_ATH_EMULATION
	prmsg("--- Skipping %s for emulation\n", __func__);
#else
	uint32_t cmd;

	if (is_drqfn() && !is_qca953x()) {
		/*
		 * Dont enable PCIe in DRQFN package as it has some issues
		 * related to PCIe
		 */
		PCI_INIT_RETURN;
	}

#if defined(CONFIG_MACH_QCA953x)
	if (ath_reg_rd(RST_BOOTSTRAP_ADDRESS) & RST_BOOTSTRAP_TESTROM_ENABLE_MASK) { 
		ath_reg_rmw_clear(RST_MISC2_ADDRESS, RST_MISC2_PERSTN_RCPHY_SET(1));

		ath_reg_wr(PCIE_PHY_REG_1_ADDRESS, PCIE_PHY_REG_1_RESET_1); 
		ath_reg_wr(PCIE_PHY_REG_3_ADDRESS, PCIE_PHY_REG_3_RESET_1); 

		ath_reg_rmw_set(PCIE_PWR_MGMT_ADDRESS, PCIE_PWR_MGMT_ASSERT_CLKREQN_SET(1));

		ath_reg_rmw_set(PCIE_PLL_CONFIG_ADDRESS, PCIE_PLL_CONFIG_PLLPWD_SET(1));

		ath_reg_rmw_set(RST_RESET_ADDRESS, RST_RESET_PCIE_RESET_SET(1));
		ath_reg_rmw_set(RST_RESET_ADDRESS, RST_RESET_PCIE_PHY_RESET_SET(1));

		ath_reg_rmw_clear(RST_CLKGAT_EN_ADDRESS, RST_CLKGAT_EN_PCIE_RC_SET(1));

		PCI_INIT_RETURN;
	} else { 
	 	 /* Honeybee -The PCIe reference clock frequency is being changed 
	  	    to vary from 99.968MHz to 99.999MHz using SS modulation */
		ath_reg_wr_nf(PCIE_PLL_DITHER_DIV_MAX_ADDRESS,
			PCIE_PLL_DITHER_DIV_MAX_EN_DITHER_SET(0x1) |
			PCIE_PLL_DITHER_DIV_MAX_USE_MAX_SET(0x1) |
			PCIE_PLL_DITHER_DIV_MAX_DIV_MAX_INT_SET(0x17) |
			PCIE_PLL_DITHER_DIV_MAX_DIV_MAX_FRAC_SET(0x3fff));

		ath_reg_wr_nf(PCIE_PLL_DITHER_DIV_MIN_ADDRESS,
			PCIE_PLL_DITHER_DIV_MIN_DIV_MIN_FRAC_SET(0x3f84)|
			PCIE_PLL_DITHER_DIV_MIN_DIV_MIN_INT_SET(0x17));
	} 
#else 

	// common for rc1 and rc2
	ath_reg_wr_nf(PCIE_PLL_DITHER_DIV_MAX_ADDRESS,
		PCIE_PLL_DITHER_DIV_MAX_EN_DITHER_SET(0x1) |
		PCIE_PLL_DITHER_DIV_MAX_USE_MAX_SET(0x1) |
		PCIE_PLL_DITHER_DIV_MAX_DIV_MAX_INT_SET(0x14) |
		PCIE_PLL_DITHER_DIV_MAX_DIV_MAX_FRAC_SET(0x3ff));

	ath_reg_wr_nf(PCIE_PLL_DITHER_DIV_MIN_ADDRESS,
		PCIE_PLL_DITHER_DIV_MIN_DIV_MIN_INT_SET(0x14));
#endif 

	ath_reg_wr_nf(PCIE_PLL_CONFIG_ADDRESS,
		PCIE_PLL_CONFIG_REFDIV_SET(1) |
		PCIE_PLL_CONFIG_BYPASS_SET(1) |
		PCIE_PLL_CONFIG_PLLPWD_SET(1));
	udelay(10000);

	ath_reg_rmw_clear(PCIE_PLL_CONFIG_ADDRESS, PCIE_PLL_CONFIG_PLLPWD_SET(1));
	udelay(1000);
	ath_reg_rmw_clear(PCIE_PLL_CONFIG_ADDRESS, PCIE_PLL_CONFIG_BYPASS_SET(1));
	udelay(1000);

#ifdef PCIE2_APP_ADDRESS
	if (!(ath_reg_rd(RST_BOOTSTRAP_ADDRESS) & RST_BOOTSTRAP_PCIE_RC_EP_SELECT_MASK)) {
		pci_rc2_init_board();
		return;
	}
#endif

	ath_reg_rmw_set(RST_RESET_ADDRESS, RST_RESET_PCIE_PHY_RESET_SET(1));
	udelay(10000);

	ath_reg_rmw_set(RST_RESET_ADDRESS, RST_RESET_PCIE_RESET_SET(1));
	udelay(10000);

#ifdef PCIE2_APP_ADDRESS
	ath_reg_rmw_clear(RST_MISC2_ADDRESS, RST_MISC2_PERSTN_RCPHY_SET(1));
	udelay(10000);
#endif

	ath_reg_wr_nf(PCIE_RESET_ADDRESS, 0);	// Put endpoint in reset
	udelay(100000);

#ifdef PCIE2_APP_ADDRESS
	ath_reg_rmw_set(RST_MISC2_ADDRESS, RST_MISC2_PERSTN_RCPHY_SET(1));
	udelay(10000);
#endif

	ath_reg_rmw_clear(RST_RESET_ADDRESS, RST_RESET_PCIE_PHY_RESET_SET(1));
	udelay(10000);

	ath_reg_rmw_clear(RST_RESET_ADDRESS, RST_RESET_PCIE_RESET_SET(1));
	udelay(10000);

	ath_reg_wr_nf(PCIE_APP_ADDRESS, PCIE_APP_PCIE_BAR_MSN_SET(1) |
					PCIE_APP_CFG_BE_SET(0xf) |
					PCIE_APP_SLV_RESP_ERR_MAP_SET(0x3f) |
					PCIE_APP_LTSSM_ENABLE_SET(1));

	cmd = PCI_COMMAND_MEMORY | PCI_COMMAND_MASTER | PCI_COMMAND_INVALIDATE |
		PCI_COMMAND_PARITY | PCI_COMMAND_SERR | PCI_COMMAND_FAST_BACK;

	ath_local_write_config(PCI_COMMAND, 4, cmd);
	ath_local_write_config(0x20, 4, 0x1ff01000);
	ath_local_write_config(0x24, 4, 0x1ff01000);

	ath_reg_wr_nf(PCIE_RESET_ADDRESS, 4);	// Pull endpoint out of reset
	udelay(100000);

	/*
	 * Check if the WLAN PCI-E H/W is present, If the
	 * WLAN H/W is not present, skip the PCI platform
	 * initialization code and return
	 */
	if (((ath_reg_rd(PCIE_RESET_ADDRESS)) & 0x1) == 0x0) {
		debug("*** Warning *** : PCIe WLAN Module not found !!!\n");
	}

#ifdef PCIE2_APP_ADDRESS
	pci_rc2_init_board();
#endif

#ifndef COMPRESSED_UBOOT
	/*
	 * Now, configure for u-boot tools
	 */

	hose.first_busno = 0;
	hose.last_busno = 0xff;

	/* System space */
	pci_set_region(	&hose.regions[0],
			0x80000000,
			0x00000000,
			32 * 1024 * 1024,
			PCI_REGION_MEM | PCI_REGION_SYS_MEMORY);

	/* PCI memory space */
	pci_set_region(	&hose.regions[1],
			0x10000000,
			0x10000000,
			128 * 1024 * 1024,
			PCI_REGION_MEM);

	hose.region_count = 2;

	pci_register_hose(&hose);

	pci_set_ops(	&hose,
			pci_hose_read_config_byte_via_dword,
			pci_hose_read_config_word_via_dword,
			ath_pci_read_config,
			pci_hose_write_config_byte_via_dword,
			pci_hose_write_config_word_via_dword,
			ath_pci_write_config);
#endif
	plat_dev_init();
#endif /* CONFIG_ATH_EMULATION */

	PCI_INIT_RETURN;
}

#ifdef PCIE2_APP_ADDRESS
void
pci_rc2_init_board (void)
{
	uint32_t	cmd;

	ath_reg_rmw_set(RST_RESET2_ADDRESS, RST_RESET2_PCIE2_PHY_RESET_SET(1));
	udelay(10000);

	ath_reg_rmw_set(RST_RESET2_ADDRESS, RST_RESET2_PCIE2_RESET_SET(1));
	udelay(10000);

	ath_reg_rmw_clear(RST_MISC2_ADDRESS, RST_MISC2_PERSTN_RCPHY2_SET(1));
	udelay(10000);

	ath_reg_wr_nf(PCIE2_RESET_ADDRESS, 0);	// Put endpoint in reset
	udelay(100000);

	ath_reg_rmw_set(RST_MISC2_ADDRESS, RST_MISC2_PERSTN_RCPHY2_SET(1));
	udelay(10000);

	ath_reg_rmw_clear(RST_RESET2_ADDRESS, RST_RESET_PCIE_PHY_RESET_SET(1));
	udelay(10000);

	ath_reg_rmw_clear(RST_RESET2_ADDRESS, RST_RESET_PCIE_RESET_SET(1));
	udelay(10000);

	ath_reg_wr_nf(PCIE2_APP_ADDRESS, PCIE2_APP_PCIE2_BAR_MSN_SET(1) |
					PCIE2_APP_CFG_BE_SET(0xf) |
					PCIE2_APP_SLV_RESP_ERR_MAP_SET(0x3f) |
					PCIE2_APP_LTSSM_ENABLE_SET(1));

	cmd = PCI_COMMAND_MEMORY | PCI_COMMAND_MASTER | PCI_COMMAND_INVALIDATE |
		PCI_COMMAND_PARITY | PCI_COMMAND_SERR | PCI_COMMAND_FAST_BACK;

	ath_local_write_config_rc2(PCI_COMMAND, 4, cmd);
	ath_local_write_config_rc2(0x20, 4, 0x1ff01000);
	ath_local_write_config_rc2(0x24, 4, 0x1ff01000);

	ath_reg_wr_nf(PCIE2_RESET_ADDRESS, 4);	// Pull endpoint out of reset
	udelay(100000);

	/*
	 * Check if the WLAN PCI-E H/W is present, If the
	 * WLAN H/W is not present, skip the PCI platform
	 * initialization code and return
	 */
	if (((ath_reg_rd(PCIE2_RESET_ADDRESS)) & 0x1) == 0x0) {
		debug("*** Warning *** : PCIe WLAN Module not found !!!\n");
		return;
	}
}
#endif
